package com.example.htmlfx.utils;

import cn.yeziji.http.tools.MRequest;
import cn.yeziji.utils.ThreadPoolConfigUtils;
import org.apache.http.client.methods.HttpGet;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @author gzkemays
 * @date 2021/8/4
 */
public class HtmlParsingUtils {
  /** 是否选择遍历 css */
  boolean switchCss;
  /** 是否选择遍历 js */
  boolean switchJs;
  /** 是否开启多线程 */
  boolean openThread;
  /** 线程池 */
  ThreadPoolExecutor threadPoolExecutor;
  /** 图片存储 */
  List<String> images = Collections.synchronizedList(new ArrayList<>());
  /** 常见的 HTML 图片格式 */
  private static final ArrayList<String> NORMAL_IMAGE_FORMAT =
      new ArrayList<String>() {
        {
          add(".bmp");
          add(".gif");
          add(".jpg");
          add(".png");
          add(".jpeg");
        }
      };

  /** 空数据 */
  private static final String EMPTY_STR = "";
  /** HTML 左屏蔽 */
  private static final String HTML_LEFT_SHIELD = "<!--";
  /** HTML 右屏蔽 */
  private static final String HTML_RIGHT_SHIELD = "-->";
  /** 正则表达式获取 () 内的内容 */
  private static final Pattern BRACKETS_PATTERN = Pattern.compile("(?<=\\()[^\\)]+");

  public static HtmlParsingUtilsBuilder builder() {
    return new HtmlParsingUtilsBuilder();
  }

  /**
   * 解析相对路径地址
   *
   * @param urls 去重后的列表
   * @param sourceUrl 资源地址
   * @return {@link List} 返回地址列表
   */
  private List<String> parseRelativelyUrl(List<String> urls, String sourceUrl) {
    List<String> tempList = new ArrayList<>();
    for (String url : urls) {
      if (url.startsWith("//")) {
        url = "http:" + url;
      }
      if (url.startsWith("../")) {
        url = parsePrevRelativeUrl(url, sourceUrl);
      }
      if (url.startsWith("./")) {
        if (sourceUrl.lastIndexOf("/") != -1) {
          url = sourceUrl + url.replaceFirst("./", EMPTY_STR);
        } else {
          url = sourceUrl + "/" + url.replaceFirst("./", EMPTY_STR);
        }
      }
      tempList.add(url);
    }
    return tempList;
  }

  /**
   * 递归解析 .. 等相对路径
   *
   * @param url 地址
   * @param sourceUrl 资源地址
   * @return {@link String} 解析后的地址
   */
  private String parsePrevRelativeUrl(String url, String sourceUrl) {
    System.out.println("sourceUrl = " + sourceUrl);
    // 相对路径的表示上级目录
    if (url.startsWith("../")) {
      url = url.replaceFirst("../", EMPTY_STR);
      return parsePrevRelativeUrl(url, sourceUrl);
    } else {
      if (sourceUrl.lastIndexOf("/") != -1) {
        url = sourceUrl + url;
      } else {
        url = sourceUrl + "/" + url;
      }
    }
    return url;
  }

  /**
   * 解析 element
   *
   * @param element 页面元素
   * @return {@link List}
   * @throws Exception 数据操作异常
   */
  private List<String> parseElement(Element element) throws Exception {
    String src = element.attr("src");
    // 有携参则截取至参数之前
    if (src.contains("?")) {
      src = src.substring(0, src.indexOf("?"));
    }
    if (element.tagName().equals("img")) {
      System.out.println("element = " + element);
    }
    if (src.lastIndexOf(".") != -1
        && NORMAL_IMAGE_FORMAT.contains(src.substring(src.lastIndexOf(".")))) {
      images.add(src);
    }
    if (switchCss && element.attr("href").contains(".css")) {
      String href = element.attr("href");
      HttpGet cssGet = MRequest.getNoDataGet(href);
      String resultFromRequest = MRequest.getResultFromRequest(cssGet);
      Matcher matcher = BRACKETS_PATTERN.matcher(resultFromRequest);
      while (matcher.find()) {
        String group = matcher.group();
        if (group.lastIndexOf(".") != -1) {
          if (NORMAL_IMAGE_FORMAT.contains(group.substring(group.lastIndexOf(".")))) {
            images.add(group);
          }
        }
      }
    }
    return images;
  }

  /**
   * 解析 HTML
   *
   * @param document 页面元素
   * @return {@link List}
   * @throws Exception 数据响应异常
   */
  private List<String> parseHtml(Document document) throws Exception {
    Elements elements = document.getAllElements();
    Future<List<String>> future = null;
    for (Element element : elements) {
      if (openThread) {
        future = threadPoolExecutor.submit(() -> parseElement(element));
      } else {
        parseElement(element);
      }
    }
    if (openThread && future != null) {
      threadPoolExecutor.getQueue().take();
      return future.get();
    }
    return images;
  }

  /**
   * 获取图片
   *
   * @param url 请求地址
   * @return {@link List}
   * @throws Exception 请求异常
   */
  public List<String> parsingImages(String url) throws Exception {
    HttpGet emptyDataGet = MRequest.getNoDataGet(url);
    String result = MRequest.getResultFromRequest(emptyDataGet);
    Document document =
        Jsoup.parse(
            result
                .replaceAll(HTML_LEFT_SHIELD, EMPTY_STR)
                .replaceAll(HTML_RIGHT_SHIELD, EMPTY_STR));
    if (openThread) {
      if (threadPoolExecutor == null) {
        threadPoolExecutor =
            ThreadPoolConfigUtils.builder()
                .corePoolSize(3)
                .maximumPoolSize(6)
                .openAllowCoreThread(10, TimeUnit.SECONDS)
                .createThreadPool()
                .build()
                .getThreadPoolExecutor();
      }
    }
    return parseRelativelyUrl(
        parseHtml(document).stream().distinct().collect(Collectors.toList()), url);
  }

  public List<String> parsingHtml(String html) throws Exception {
    Document document = Jsoup.parse(html);
    if (openThread) {
      if (threadPoolExecutor == null) {
        threadPoolExecutor =
            ThreadPoolConfigUtils.builder()
                .corePoolSize(3)
                .maximumPoolSize(6)
                .openAllowCoreThread(10, TimeUnit.SECONDS)
                .createThreadPool()
                .build()
                .getThreadPoolExecutor();
      }
    }
    return parseHtml(document).stream().distinct().collect(Collectors.toList());
  }

  public static final class HtmlParsingUtilsBuilder {
    boolean switchCss = false;
    boolean switchJs = false;
    boolean openThread = false;

    private HtmlParsingUtilsBuilder() {}

    public HtmlParsingUtilsBuilder switchCss(boolean switchCss) {
      this.switchCss = switchCss;
      return this;
    }

    public HtmlParsingUtilsBuilder switchJs(boolean switchJs) {
      this.switchJs = switchJs;
      return this;
    }

    public HtmlParsingUtilsBuilder openThread(boolean openThread) {
      this.openThread = openThread;
      return this;
    }

    public HtmlParsingUtils build() {
      HtmlParsingUtils htmlParsingUtils = new HtmlParsingUtils();
      htmlParsingUtils.switchJs = this.switchJs;
      htmlParsingUtils.openThread = this.openThread;
      htmlParsingUtils.switchCss = this.switchCss;
      return htmlParsingUtils;
    }
  }
}
